package tcln.gxtx.client.widget.grid.renderer.widget;

import java.util.HashMap;
import java.util.Map.Entry;

import tcln.gxtx.client.utils.RendererPredicate;
import tcln.gxtx.client.utils.assertion.GwtAssert;
import tcln.gxtx.client.widget.treegrid.AbstractTreeGrid;
import tcln.gxtx.client.widget.treegrid.model.AbstractTreeModel;

import com.extjs.gxt.ui.client.data.ModelData;
import com.extjs.gxt.ui.client.event.GridEvent;
import com.extjs.gxt.ui.client.store.ListStore;
import com.extjs.gxt.ui.client.widget.LayoutContainer;
import com.extjs.gxt.ui.client.widget.grid.ColumnConfig;
import com.extjs.gxt.ui.client.widget.grid.ColumnData;
import com.extjs.gxt.ui.client.widget.grid.Grid;
import com.extjs.gxt.ui.client.widget.layout.FlowLayout;

/**
 * A composit grid-cell renderer which composes of several widget renderers. The main goal of this 'group' renderer is
 * to support render-by-context. This means, base on current rendering cell, the rendering process may be different
 * thanks to inner renderers.
 * 
 * @param <M>
 *            Model data type.
 * @author Truong Chau Lien Ngoc (lienngoc@gmail.com)
 */
public class ContextualWidgetGridCellRenderer<M extends AbstractTreeModel> extends AbstractWidgetGridCellRenderer<M> {
    private static final LayoutContainer EMPTY_CONTAINER = new LayoutContainer(new FlowLayout(0));
    private static final RendererPredicate DEFAULT_PREDICATE = new AlwaysTruePredicate();

    private HashMap<RendererPredicate, WidgetGridCellRenderer<M>> innerRendererMap;

    /**
     * Constructs this renderer from a column.
     * 
     * @param column
     *            owner column config.
     */
    public ContextualWidgetGridCellRenderer(ColumnConfig column) {
        super(column);

        innerRendererMap = new HashMap<RendererPredicate, WidgetGridCellRenderer<M>>();
    }

    /**
     * {@inheritDoc}
     * <p>
     * NOTE: This method always returns <code>false</code> in this case.
     */
    @Override
    public final boolean isInContextualRenderer() {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void handleCellMouseDown(GridEvent<M> e) {
        WidgetGridCellRenderer<M> innerRenderer = getRenderer(e.getModel(), ownerColumn.getId(), e.getRowIndex(), e
                .getColIndex(), ownerTree.getCurrentStore(), ownerGrid);

        if (innerRenderer != null) {
            if (!innerRenderer.isInitialized()) {
                innerRenderer.init(ownerGrid);
            }
            innerRenderer.handleCellMouseDown(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object render(M model, String property, ColumnData config, int rowIndex, int colIndex, ListStore<M> store,
            Grid<M> grid) {
        WidgetGridCellRenderer<M> renderer = getRenderer(model, property, rowIndex, colIndex, store, grid);
        if (renderer != null) {
            return renderer.render(model, property, config, rowIndex, colIndex, store, grid);
        } else {
            return EMPTY_CONTAINER;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void setOwnerTree(AbstractTreeGrid<M> ownerTree) {
        super.setOwnerTree(ownerTree);

        for (Entry<RendererPredicate, WidgetGridCellRenderer<M>> entry : innerRendererMap.entrySet()) {
            WidgetGridCellRenderer<M> innerRenderer = entry.getValue();
            innerRenderer.setOwnerTree(ownerTree);
        }
    }

    /**
     * Adds a renderer to current group with a predicate for renderer identification.
     * 
     * @param predicate
     *            Predicate for renderer selection, required.
     * @param renderer
     *            Custom renderer. If this renderer is not specified (i.e. <code>null</code>), an empty widget is used
     *            for rendering.
     */
    public final void addRenderer(WidgetGridCellRenderer<M> renderer, RendererPredicate predicate) {
        GwtAssert.ARGUMENTS.notNull(predicate, "Sample model data must NOT be null");
        if (renderer != null) {
            innerRendererMap.put(predicate, renderer);
        }
    }

    /**
     * Adds the default renderer to current group.
     * 
     * @param renderer
     *            Custom renderer. If this renderer is not specified (i.e. <code>null</code>), an empty widget is used
     *            for rendering.
     */
    public final void addDefaultRenderer(WidgetGridCellRenderer<M> renderer) {
        addRenderer(renderer, DEFAULT_PREDICATE);
    }

    protected WidgetGridCellRenderer<M> getRenderer(M model, String property, int rowIndex, int colIndex,
            ListStore<M> store, Grid<M> grid) {
        for (Entry<RendererPredicate, WidgetGridCellRenderer<M>> entry : innerRendererMap.entrySet()) {
            if (entry.getKey().evaluate(model, property, rowIndex, colIndex, store, grid)) {
                return entry.getValue();
            }
        }
        return null;
    }

    private static class AlwaysTruePredicate implements RendererPredicate {
        @Override
        public boolean evaluate(ModelData model, String property, int rowIndex, int colIndex, ListStore<?> store,
                Grid<?> grid) {
            return true;
        }
    }
}
